﻿using System;
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Linq;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using Microsoft.WowAddonStudio.FrameXml.Components.Descriptors;
using Microsoft.WowAddonStudio.FrameXml.Components.Events;
using Microsoft.WowAddonStudio.FrameXml.Components.Support;
using Microsoft.WowAddonStudio.FrameXml.Serialization;
using Microsoft.WowAddonStudio.FrameXml.Serialization.LayoutFrames;

namespace Microsoft.WowAddonStudio.FrameXml.Components.Designers
{
    /// <summary>
    /// Extends the design mode behavior of a System.Windows.Forms.Control that supports nested controls.
    /// </summary>
    public class LayoutFrameContainerDesigner : ParentControlDesigner, ILayoutFrameDesignerEvents
    {
        private DesignerActionListCollection actionLists;

        /// <summary>
        /// Occurs when [initialized new component].
        /// </summary>
        public event EventHandler<FrameDesignerEventArgs> InitializedNewComponent;

        /// <summary>
        /// Gets the design-time action lists supported by the component associated with the designer.
        /// </summary>
        /// <value></value>
        /// <returns>The design-time action lists supported by the component associated with the designer.</returns>
        public override DesignerActionListCollection ActionLists
        {
            get
            {
                var frameXmlControl = Component as IFrameXmlControl;

                if (actionLists == null && frameXmlControl != null && frameXmlControl.HasActions)
                {
                    actionLists = new DesignerActionListCollection
                                      {
                                          new FrameXmlDesignerActionList(frameXmlControl)
                                      };
                }

                return actionLists;
            }
        }

        /// <summary>
        /// Initializes a newly created component.
        /// </summary>
        /// <param name="defaultValues">
        /// A name/value dictionary of default values to apply to properties.
        /// May be null if no default values are specified.
        /// </param>
        public override void InitializeNewComponent(IDictionary defaultValues)
        {
            base.InitializeNewComponent(defaultValues);
            
            //Reinitialize inherited controls if presents
            var componentFactory = (FrameXmlComponentFactory) GetService(typeof (FrameXmlComponentFactory));
            if (componentFactory != null)
                componentFactory.ReinitializeComponent(Control as IFrameXmlComponent);

            if (InitializedNewComponent != null)
                InitializedNewComponent(this, new FrameDesignerEventArgs(Control));
        }

        /// <summary>
        /// Creates a method signature in the source code file for the default event 
        /// on the component and navigates the user's cursor to that location.
        /// </summary>
        /// <exception cref="T:System.ComponentModel.Design.CheckoutException">An attempt to check out a file that is checked into a source code management program failed.</exception>
        public override void DoDefaultAction()
        {
            base.DoDefaultAction();

            //Get the IEventBindingService from container
            var eventBindingService = (IEventBindingService) GetService(typeof (IEventBindingService));
            if (eventBindingService != null)
            {
                //Get DefaultFrameXmlEvent attribute from control's class
                var eventGroupAttribute = Control.GetType().GetCustomAttributes(typeof (EventGroupAttribute), false).OfType<EventGroupAttribute>().SingleOrDefault();

                //Check for existing attribute
                if (eventGroupAttribute != null)
                {
                    //Get default EventName from attribute
                    EventName defaultEventName = eventGroupAttribute.DefaultEventName;

                    //Get EventDescriptor instance for the deafult event
                    FrameXmlEventDescriptor frameXmlEventDescriptor = TypeDescriptor.GetEvents(Control).OfType<FrameXmlEventDescriptor>().
                        Where(eventDescriptor => eventDescriptor.EventName == defaultEventName).FirstOrDefault();

                    if (frameXmlEventDescriptor != null)
                    {
                        //Do not add default event for read only (inherited) control
                        ReadOnlyAttribute readOnlyAttribute = frameXmlEventDescriptor.Attributes.OfType<ReadOnlyAttribute>().FirstOrDefault();
                        if (readOnlyAttribute != null && readOnlyAttribute.IsReadOnly)
                            return;

                        //Convert EventDescriptor to PropertyDescriptor 
                        var propertyDescriptor = (FrameXmlEventPropertyDescriptor) eventBindingService.GetEventProperty(frameXmlEventDescriptor);

                        //Create unique method name for event handler
                        string uniqueMethodName = eventBindingService.CreateUniqueMethodName(Control, frameXmlEventDescriptor);

                        //Set the new event handler or jump to teh existing one
                        propertyDescriptor.SetValue(Control, uniqueMethodName);
                    }
                }
            }
        }

        /// <summary>
        /// Indicates if this designer's control can be parented by the control of the specified designer.
        /// </summary>
        /// <param name="parentDesigner">The <see cref="T:System.ComponentModel.Design.IDesigner"/> that manages the control to check.</param>
        /// <returns>
        /// true if the control managed by the specified designer can parent the control managed by this designer; otherwise, false.
        /// </returns>
        public override bool CanBeParentedTo(IDesigner parentDesigner)
        {
            var component = (IFrameXmlComponent) this.Control;

            if(component.SerializationObject is LayoutFrame)
            {
                var layoutFrame = (LayoutFrame)component.SerializationObject;

                if(layoutFrame.IsInVirtualState)
                {
                    // Controls which are in virtual state cannot movable to the Ui.
                    if(parentDesigner.Component.GetType() == typeof(UiControl))
                        return false;
                }

                if (layoutFrame.Virtual)
                    return false;
            }

            return base.CanBeParentedTo(parentDesigner);
        }

        /// <summary>
        /// Indicates whether the control managed by the specified designer can be a child of the control managed by this designer.
        /// </summary>
        /// <param name="controlDesigner">The designer for the control to test.</param>
        /// <returns>
        /// true if the control managed by the specified designer can be a child of the control managed by this designer; otherwise, false.
        /// </returns>
        public override bool CanParent(ControlDesigner controlDesigner)
        {
            return CanParent() && base.CanParent(controlDesigner);
        }

        /// <summary>
        /// Indicates whether the specified control can be a child of the control managed by this designer.
        /// </summary>
        /// <param name="control">The <see cref="T:System.Windows.Forms.Control"/> to test.</param>
        /// <returns>
        /// true if the specified control can be a child of the control managed by this designer; otherwise, false.
        /// </returns>
        public override bool CanParent(Control control)
        {
            return CanParent() && base.CanParent(control);
        }

        /// <summary>
        /// Provides core functionality for all the <see cref="M:System.Windows.Forms.Design.ParentControlDesigner.CreateTool(System.Drawing.Design.ToolboxItem)"/> methods.
        /// </summary>
        /// <param name="tool">The <see cref="T:System.Drawing.Design.ToolboxItem"/> to create a component from.</param>
        /// <param name="x">The horizontal position, in design-time view coordinates, of the location of the left edge of the tool, if a size is specified; the horizontal position of the center of the tool, if no size is specified.</param>
        /// <param name="y">The vertical position, in design-time view coordinates, of the location of the top edge of the tool, if a size is specified; the vertical position of the center of the tool, if no size is specified.</param>
        /// <param name="width">The width of the tool. This parameter is ignored if the <paramref name="hasSize"/> parameter is set to false.</param>
        /// <param name="height">The height of the tool. This parameter is ignored if the <paramref name="hasSize"/> parameter is set to false.</param>
        /// <param name="hasLocation">true if a location for the component is specified; false if the component is to be positioned in the center of the currently selected control.</param>
        /// <param name="hasSize">true if a size for the component is specified; false if the default height and width values for the component are to be used.</param>
        /// <returns>
        /// An array of components created from the tool.
        /// </returns>
        protected override IComponent[] CreateToolCore(System.Drawing.Design.ToolboxItem tool, int x, int y, int width, int height, bool hasLocation, bool hasSize)
        {
            if (!CanParent())
                return new IComponent[0];

            return base.CreateToolCore(tool, x, y, width, height, hasLocation, hasSize);
        }

        /// <summary>
        /// Gets a list of <see cref="T:System.Windows.Forms.Design.Behavior.SnapLine"/> objects representing significant alignment points for this control.
        /// </summary>
        /// <value></value>
        /// <returns>
        /// A list of <see cref="T:System.Windows.Forms.Design.Behavior.SnapLine"/> objects representing significant alignment points for this control.
        /// </returns>
        public override IList SnapLines
        {
            get
            {
                if (!CanParent())
                    return new Array[0];

                return base.SnapLines;
            }
        }

        /// <summary>
        /// Determines whether [is root parent virtual].
        /// </summary>
        /// <returns>
        /// 	<c>true</c> if [is root parent virtual]; otherwise, <c>false</c>.
        /// </returns>
        private bool CanParent()
        {
            bool canParent = true;

            var component = (IFrameXmlComponent)this.Control;

            if (component != null)
            {
                while (component.Name != "Ui")
                {
                    var layoutFrame = (LayoutFrame)component.SerializationObject;

                    if (layoutFrame != null)
                    {
                        if (layoutFrame.LayoutHidden || layoutFrame.Locked)
                        {
                            canParent = false;
                            break;
                        }
                    }

                    component = component.Parent;
                }
            }

            return canParent;
        }
    }
}